﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System.Text;
using System.Threading;

namespace EventbriteService.Factory
{
    public sealed class EventbriteFactory<T> : IEventbriteFactory<T> where T : class
    {
        private readonly static Assembly _activeProvider = null;

        private Dictionary<string, Type> _targets;
        private Type _requestedType = typeof(T);


        static EventbriteFactory()
        {
            string key = typeof(T).Name;
            string providerName = System.Configuration.ConfigurationManager.AppSettings[key];
            if (!String.IsNullOrEmpty(providerName))
            {
                  _activeProvider = Assembly.Load(providerName);
            }
            else
            {
                _activeProvider = Assembly.GetAssembly(typeof(T));
            }
        }
        
        public EventbriteFactory()
        {
            _targets = new Dictionary<string, Type>();
            AddTypesFromAssembly();
        }

        #region IEventbriteFactory<T> Members

        public T GetInstance(string key, params object[] vals)
        {
            Type concreteType = null;
            if (!string.IsNullOrEmpty(key))
            {
                _targets.TryGetValue(key, out concreteType);
            }
            else
            {
                concreteType = _targets.Values.ElementAt(0);
            }

            return ConcreteTypeActivator<T>.CreateInstance(concreteType.GetConstructor(GetConstructorParams(vals)), vals);

        }

        public T GetInstance(params object[] vals)
        {
            return GetInstance(null, vals);
        }

        #endregion

        private Type[] GetConstructorParams(object[] vals)
        {
            Type[] constructorParams = new Type[vals.Length];
            for (int i = 0; i < vals.Length; i++)
            {
                constructorParams[i] = vals[i].GetType();
            }
            return constructorParams;
        }

        private void AddTypesFromAssembly()
        {
            var queryTargets = _activeProvider.GetTypes()
                                       .Where(t => t.IsClass
                                                   && !t.IsAbstract
                                                   && _requestedType.IsAssignableFrom(t)
                                                   ).ToArray();

            foreach (var item in queryTargets)
            {
                var array = item.GetCustomAttributes(typeof(CreateWithNameAttribute), false);
                var attr = array.Length > 0 ? array[0] as CreateWithNameAttribute : null;

                Type type;
                var key = attr != null ? attr.Name : item.Name.ToLower();
                if (!_targets.TryGetValue(key, out type))
                {
                    _targets.Add(key, item);
                }
            }
        }
    }
}
